﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using Microsoft.Xna.Framework.Input;

namespace Tomahawk.Runtime.Logic.UI
{

    /// <summary>
    /// An UIScene represents an UI canvas, that occupies the whole screen.
    /// </summary>
    public class XUIScene : XUIControl
    {
        /// <summary>
        /// Constructor logic
        /// </summary>
        public XUIScene()
        {
            controlDictionary = new Dictionary<string, XUIControl>();

            //default input management configuration
            InputConfig.Enabled = false;
            InputConfig.Keyboard = false;
            InputConfig.Player = false;
        }

        #region "Control Indexing"

        /// <summary>
        /// Indexes all the UIScene controls by its name
        /// </summary>
        private Dictionary<string, XUIControl> controlDictionary;

        /// <summary>
        /// Traverse all child controls of the given control, and add them all to the dictonary index
        /// </summary>
        private void indexChildControls(XUIControl startControl)
        {
            //Index the controls that has a name assigned
            foreach (XUIControl control in this.ChildControls)
            {
                //if the control has UIName
                if ((control.UIName != null) && (control.UIName != ""))
                {
                    //add to dictionary
                    addToControlDictionary(control);
                }
            }

            //Index the controls that has no name assigned
            foreach (XUIControl control in this.ChildControls)
            {
                //if the control has no UIName
                if ((control.UIName == null) || (control.UIName == ""))
                {
                    //autoassing a name
                    this.autogenerateName(control);

                    //add to dictionary
                    this.addToControlDictionary(control);
                }
            }
        }

        /// <summary>
        /// Add a control to the name dictionary. Controls the possible error situations.
        /// Then try to add all of it child controls.
        /// </summary>
        private void addToControlDictionary(XUIControl control)
        {
            try
            {
                //if nonamed, autogenerate
                if ((control.UIName == null) || (control.UIName == ""))
                    this.autogenerateName(control);

                //add the control to the dictionary
                controlDictionary.Add(control.UIName, control);

                //add its child control recursivelly
                foreach (XUIControl childcontrol in control.ChildControls)
                {
                    this.indexChildControls(childcontrol);
                }
            }
            catch (ArgumentException)
            {
                throw new DuplicatedUIName(this, control);
            }
        }

        /// <summary>
        /// Utility counter for autogerated names
        /// </summary>
        static int autogeneratedNameCounter = 0;

        /// <summary>
        /// Asing an autogenerate name to the control
        /// </summary>
        internal void autogenerateName(XUIObject control)
        {
            bool found = false;
            string proposedName = "";
            while (!found)
            {
                proposedName = String.Format("{0}_{1}",
                    control.GetType().Name, autogeneratedNameCounter.ToString("000"));
                if (!this.controlDictionary.Keys.Contains(proposedName)) found = true;
                autogeneratedNameCounter++;
            }
            //assign the name found
            control.UIName = proposedName;
        }

        /// <summary>
        /// Check navegation references by name on all placeable controls recursivelly
        /// </summary>
        internal void checkFocusRenferences(XUIControl control)
        {
            //call the method recursivelly
            foreach (XUIControl child in control.ChildControls)
            {
                checkFocusRenferences(child);
            }

            //if the control is a placeable
            XUIPlaceableControl placeable = control as XUIPlaceableControl;
            if (placeable != null)
            {
                // DOWN REFERENCE
                if ((placeable.FocusReferences.Down != null) && (placeable.FocusReferences.Down != ""))
                {
                    XUIControl outControl = null;
                    if (!this.controlDictionary.TryGetValue(placeable.FocusReferences.Down, out outControl))
                    {
                        //warn the error
                        string error = String.Format("Control '{0}' not found, on focus references in control '{1}', on scene '{2}'",
                            placeable.FocusReferences.Down, placeable.UIName, this.UIName);
                        Engine.Instance.DebugConsole.Warn(error);
                        //leave the reference empty
                        placeable.FocusReferences.Down = "";
                    }
                }

                // UP REFERENCE
                if ((placeable.FocusReferences.Up != null) && (placeable.FocusReferences.Up != ""))
                {
                    XUIControl outControl = null;
                    if (!this.controlDictionary.TryGetValue(placeable.FocusReferences.Up, out outControl))
                    {
                        //warn the error
                        string error = String.Format("Control '{0}' not found, on focus references in control '{1}', on scene '{2}'",
                            placeable.FocusReferences.Up, placeable.UIName, this.UIName);
                        Engine.Instance.DebugConsole.Warn(error);
                        //leave the reference empty
                        placeable.FocusReferences.Up = "";
                    }
                }

                // LEFT REFERENCE
                if ((placeable.FocusReferences.Left != null) && (placeable.FocusReferences.Left != ""))
                {
                    XUIControl outControl = null;
                    if (!this.controlDictionary.TryGetValue(placeable.FocusReferences.Left, out outControl))
                    {
                        //warn the error
                        string error = String.Format("Control '{0}' not found, on focus references in control '{1}', on scene '{2}'",
                            placeable.FocusReferences.Left, placeable.UIName, this.UIName);
                        Engine.Instance.DebugConsole.Warn(error);
                        //leave the reference empty
                        placeable.FocusReferences.Left = "";
                    }
                }

                // RIGHT REFERENCE
                if ((placeable.FocusReferences.Right != null) && (placeable.FocusReferences.Right != ""))
                {
                    XUIControl outControl = null;
                    if (!this.controlDictionary.TryGetValue(placeable.FocusReferences.Right, out outControl))
                    {
                        //warn the error
                        string error = String.Format("Control '{0}' not found, on focus references in control '{1}', on scene '{2}'",
                            placeable.FocusReferences.Right, placeable.UIName, this.UIName);
                        Engine.Instance.DebugConsole.Warn(error);
                        //leave the reference empty
                        placeable.FocusReferences.Right = "";
                    }
                }
            }
        }

        /// <summary>
        /// Get a control from the dictionary
        /// </summary>
        public XUIControl GetControlByName(string controlName)
        {
            XUIControl retVal = null;
            this.controlDictionary.TryGetValue(controlName, out retVal);
            if (retVal == null)
            {
                Engine.Instance.DebugConsole.Warn("Control not found on UIScene: " + controlName);
            }
            return retVal;
        }

        protected override void OnControlAddded(XUIControl control)
        {
            base.OnControlAddded(control);
            //add to the dictionary
            this.addToControlDictionary(control);
        }

        #endregion

        /// <summary>
        /// Initialization
        /// </summary>
        protected override void OnInitialize()
        {
            base.OnInitialize();

            //note: at this point all child controls are initialized

            //index all child controls in the dictionary
            this.indexChildControls(this);

            //check for references by name on focus references
            this.checkFocusRenferences(this);

            //this logic object requires updates
            this.UpdateRequired = true;

            //focus the first control
            this.FocusFirstControl();
        }

        /// <summary>
        /// Scene update
        /// </summary>
        protected override void OnUpdate(float elapsedTime)
        {
            base.OnUpdate(elapsedTime);

            //manages input
            this.processInput(elapsedTime);

        }

        #region "Activation"

        private bool active = true;

        public bool Active
        {
            get { return this.active; }
        }

        public void Activate()
        {
            this.active = true;
            this.OnActivate();
        }

        protected virtual void OnActivate() { }

        public void Deactivate()
        {
            this.active = false;
            this.OnDeactivate();
        }

        protected virtual void OnDeactivate() { }

        #endregion

        #region "Input management"

        /// <summary>
        /// This structs defines filters how can produce input events againts this Scene
        /// </summary>
        public struct HandleInputConfig
        {
            [XmlAttribute("enabled")]
            public bool Enabled;

            [XmlAttribute("keyboard")]
            public bool Keyboard;

            [XmlAttribute("player")]
            public bool Player;
        }

        [XmlElement("Input")]
        public HandleInputConfig InputConfig;

        private float ignoreInputRecentActivation = 0;

        /// <summary>
        /// Process input
        /// </summary>
        private void processInput(float elapsedTime)
        {
            if (this.InputConfig.Enabled)
            {
                if (this.active)
                {
                    if (this.ignoreInputRecentActivation == 0)
                    {
                        if (this.InputConfig.Keyboard)
                        {
                            processKeyboardInput(elapsedTime);
                        }
                        if (this.InputConfig.Player)
                        {
                            //TODO: process input from players, and gamepads
                        }
                    }
                    else
                    {
                        this.ignoreInputRecentActivation -= elapsedTime;
                        if (this.ignoreInputRecentActivation < 0) this.ignoreInputRecentActivation = 0;
                    }
                }
                else
                {
                    this.ignoreInputRecentActivation = 0.1f;
                }
            }
        }

        private KeyboardState _oldKeybState;

        private void processKeyboardInput(float elapsedTime)
        {
            KeyboardState newKeybState = Microsoft.Xna.Framework.Input.Keyboard.GetState();

            // LEFT KEY
            if (!_oldKeybState.IsKeyDown(Keys.Left) && newKeybState.IsKeyDown(Keys.Left))
            {
                if (focusedControl != null)
                    this.FocusNavigatePrev(focusedControl);
            }

            //RIGHT KEY
            if (!_oldKeybState.IsKeyDown(Keys.Right) && newKeybState.IsKeyDown(Keys.Right))
            {
                if (focusedControl != null)
                    this.FocusNavigateNext(focusedControl);
            }

            //UP KEY
            if (!_oldKeybState.IsKeyDown(Keys.Up) && newKeybState.IsKeyDown(Keys.Up))
            {
                if (focusedControl != null)
                    this.FocusNavigateUp(focusedControl);
            }

            //DOWN KEY
            if (!_oldKeybState.IsKeyDown(Keys.Down) && newKeybState.IsKeyDown(Keys.Down))
            {
                if (focusedControl != null)
                    this.FocusNavigateDown(focusedControl);
            }

            //ENTER KEY
            if (!_oldKeybState.IsKeyDown(Keys.Enter) && newKeybState.IsKeyDown(Keys.Enter))
            {
                if (focusedControl != null)
                    this.focusedControl.UserActivate();
            }

            //finished with keyb status
            _oldKeybState = newKeybState;
        }

        #endregion

        #region "Focus Management"
        
        /// <summary>
        /// Stores the control that its currently focused
        /// </summary>
        XUIPlaceableControl focusedControl = null;

        /// <summary>
        /// Focus the first focusable control of the UIScene
        /// </summary>
        private void FocusFirstControl()
        {
            bool found = false;
            foreach (XUIControl control in this.controlDictionary.Values)
            {
                if (!found)
                {
                    XUIPlaceableControl placeable = control as XUIPlaceableControl;
                    if ((placeable != null) && (placeable.CanHaveFocus))
                    {
                        placeable.Focus();
                        found = true;
                    }
                }
            }
        }

        /// <summary>
        /// Set a new focused control. It's called from the control itself
        /// </summary>
        internal void SetFocusedControl(XUIPlaceableControl control)
        {
            //unset focus on previously focused control
            if (focusedControl != null)
            {
                focusedControl.Unfocus();
            }

            //set the new focused control
            focusedControl = control;
        }

        /// <summary>
        /// Focus the next focusable control
        /// </summary>
        internal void FocusNavigateNext(XUIPlaceableControl sourceControl)
        {
            //has the currently focused control defined which is the next one?
            XUIPlaceableControl candidateControl = null;
            if ((sourceControl.FocusReferences.Right != null) && (sourceControl.FocusReferences.Right != ""))
            {
                candidateControl = this.controlDictionary[focusedControl.FocusReferences.Right] as XUIPlaceableControl;
            }
            else if ((sourceControl.FocusReferences.Down != null) && (sourceControl.FocusReferences.Down != ""))
            {
                candidateControl = this.controlDictionary[sourceControl.FocusReferences.Down] as XUIPlaceableControl;
            }

            //try focusing the candidate, if it's not focusable, this method will be called again
            if (candidateControl != null)
            {
                candidateControl.Focus();
            }
            else
            {
                /*
                //
                //No candidate control was found, try focusing next focusable control on the array
                //

                // count focusable controls

                int otherFocusableControls = 0;
                foreach (XUIControl focusable in this.controlDictionary.Values)
                {
                    XUIPlaceableControl placeableCandidate = focusable as XUIPlaceableControl;
                    if (placeableCandidate != null)
                    {
                        if ((placeableCandidate != sourceControl) && (placeableCandidate.CanHaveFocus = true))
                        {
                            //count as a valid candidate
                            otherFocusableControls++;
                        }
                    }
                }

                // if there are any, simply asign the first one found after the source one.
                // if there is no focusable control in the array after the source one, 
                // give it another try from the begining

                if (otherFocusableControls > 0)
                {
                    bool sourceFound = false;
                    bool focusableFound = false;

                    for (int i = 0; i < 2; i++)
                    {
                        if (!focusableFound)
                        {
                            foreach (XUIControl focusable in this.controlDictionary.Values)
                            {
                                if (focusable == sourceControl)
                                {
                                    sourceFound = true;
                                }
                                else
                                {
                                    XUIPlaceableControl placeableCandidate = focusable as XUIPlaceableControl;
                                    if ((sourceFound) && (placeableCandidate != null) && (placeableCandidate.CanHaveFocus))
                                    {
                                        sourceFound = false;
                                        focusableFound = true;
                                        placeableCandidate.Focus();
                                    }
                                }
                            }
                        }
                    }
                }
                else
                {
                    //keep the source control as the focused one, and warn the user
                    if (sourceControl.CanHaveFocus) sourceControl.Focus();
                    Engine.Instance.DebugConsole.Warn("No other focusable controls were found!");
                }
                */
            }
        }

        /// <summary>
        /// Focus the next focusable control
        /// </summary>
        internal void FocusNavigatePrev(XUIPlaceableControl sourceControl)
        {
            //has the currently focused control defined which is the next one?
            XUIPlaceableControl candidateControl = null;
            if ((sourceControl.FocusReferences.Left != null) && (sourceControl.FocusReferences.Left != ""))
            {
                candidateControl = this.controlDictionary[focusedControl.FocusReferences.Left] as XUIPlaceableControl;
            }
            else if ((sourceControl.FocusReferences.Up != null) && (sourceControl.FocusReferences.Up != ""))
            {
                candidateControl = this.controlDictionary[sourceControl.FocusReferences.Up] as XUIPlaceableControl;
            }

            //try focusing the candidate, if it's not focusable, this method will be called again
            if (candidateControl != null)
            {
                candidateControl.Focus();
            }
            else
            {
                /*
                //
                //No candidate control was found, try focusing next focusable control on the array
                //

                // count focusable controls

                int otherFocusableControls = 0;
                foreach (XUIControl focusable in this.controlDictionary.Values)
                {
                    XUIPlaceableControl placeableCandidate = focusable as XUIPlaceableControl;
                    if (placeableCandidate != null)
                    {
                        if ((placeableCandidate != sourceControl) && (placeableCandidate.CanHaveFocus = true))
                        {
                            //count as a valid candidate
                            otherFocusableControls++;
                        }
                    }
                }

                // if there are any, simply asign the first one found after the source one.
                // if there is no focusable control in the array after the source one, 
                // give it another try from the begining

                if (otherFocusableControls > 0)
                {
                    bool sourceFound = false;
                    bool focusableFound = false;

                    foreach (XUIControl focusable in this.controlDictionary.Values)
                    {
                        if (focusable == sourceControl)
                        {
                            sourceFound = true;
                        }
                        else
                        {
                            XUIPlaceableControl placeableCandidate = focusable as XUIPlaceableControl;
                            if ((!sourceFound) && (placeableCandidate != null) && (placeableCandidate.CanHaveFocus))
                            {
                                focusableFound = true;
                                placeableCandidate.Focus();
                            }
                        }
                    }

                }
                else
                {
                    //keep the source control as the focused one, and warn the user
                    if (sourceControl.CanHaveFocus) sourceControl.Focus();
                    Engine.Instance.DebugConsole.Warn("No other focusable controls were found!");
                }
                */
            }
        }
        
        /// <summary>
        /// Focus the upside focusable control
        /// </summary>
        internal void FocusNavigateUp(XUIPlaceableControl sourceControl)
        {
            //has the currently focused control defined which is the next one?
            XUIPlaceableControl candidateControl = null;
            if ((sourceControl.FocusReferences.Up != null) && (sourceControl.FocusReferences.Up != ""))
            {
                candidateControl = this.controlDictionary[sourceControl.FocusReferences.Up] as XUIPlaceableControl;
            }

            //try focusing the candidate, if it's not focusable, this method will be called again
            if (candidateControl != null)
            {
                candidateControl.Focus();
            }
        }

        /// <summary>
        /// Focus the upside focusable control
        /// </summary>
        internal void FocusNavigateDown(XUIPlaceableControl sourceControl)
        {
            //has the currently focused control defined which is the next one?
            XUIPlaceableControl candidateControl = null;
            if ((sourceControl.FocusReferences.Down != null) && (sourceControl.FocusReferences.Down != ""))
            {
                candidateControl = this.controlDictionary[sourceControl.FocusReferences.Down] as XUIPlaceableControl;
            }

            //try focusing the candidate, if it's not focusable, this method will be called again
            if (candidateControl != null)
            {
                candidateControl.Focus();
            }
        }

        #endregion

        #region "Notifications"

        /// <summary>
        /// Handle this event to notify the client code with messages
        /// </summary>
        public event ControlNotification SceneNotification;

        /// <summary>
        /// Notify a message to client code
        /// </summary>
        protected void Notify(String message)
        {
            if (this.SceneNotification != null) this.SceneNotification(message);
        }

        #endregion
    
        #region "Exceptions"

        /// <summary>
        /// Raises when a duplicated uiname is used in the same UIScene
        /// </summary>
        public class DuplicatedUIName : UIBaseException
        {
            public DuplicatedUIName(XUIScene scene, XUIControl control) : base(Tomahawk.Runtime.Debug.Exceptions.ExceptionSeverity.Error, 
                                string.Format("Duplicated UIName '{0}' found on UIScene '{1}'", control.UIName, scene.UIName), null) {}
        }

        #endregion

    }

}
